package br.usp.ime.origami.foldcut.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import br.usp.ime.origami.foldcut.geometry.Line;
import br.usp.ime.origami.foldcut.structures.Pair;
import br.usp.ime.origami.foldcut.structures.simplegraph.Edge;
import br.usp.ime.origami.foldcut.structures.winged.WingedEdge;
import br.usp.ime.origami.foldcut.structures.winged.WingedFace;
import br.usp.ime.origami.foldcut.util.MoleculeEdge.EdgeType;
import br.usp.ime.origami.model.Point;
import br.usp.ime.origami.model.SimplePoint;
import br.usp.ime.origami.primitives.AnglePrimitives;
import br.usp.ime.origami.primitives.BasicPrimitives;
import br.usp.ime.origami.primitives.SegmentPrimitives;

public class QuadrilateralFolder
{

	private Pair<Point> bissectorOriginPoints;

	private Pair<Point> tangentIntersectionPoints;

	private List<ExtraEdge> extras = new ArrayList<ExtraEdge>();

	private List<BissectorEdge> bissectors = new ArrayList<BissectorEdge>();

	private List<FixedEdge> fixeds = new ArrayList<FixedEdge>();

	private List<Point> quadrilateral = new ArrayList<Point>();

	private List<Point> quadrilateralTangent = new ArrayList<Point>();

	private List<TangentEdge> tangents;

	private WingedFace face;

	public Molecule foldQuadrilateral(WingedFace face)
	{
		if (face.size() != 8)
		{
			throw new IllegalArgumentException();
		}

		this.face = face;

		calculateFixedEdges();
		calculatePoints();
		calculateTangents();

		// calculateBissectors();

		assert fixeds.size() == 8;
		assert tangents.size() == 4;
		assert bissectors.size() == 4;
		assert extras.size() == 8;

		return new Molecule(face, fixeds, tangents, bissectors, extras);
	}

	private void calculateBissectors()
	{
		List<Line> bissectorLines = new ArrayList<Line>();

		for (int i = 0; i < 8; i += 2)
		{
			Line l1 = new Line(fixeds.get((i + 7) % 8));
			Line l2 = new Line(fixeds.get(i));
			bissectorLines.add(l1.getBissector(l2));
		}

	}

	void calculateTangents()
	{
		List<Line> perpendiculars = new ArrayList<Line>();
		for (int i = 0; i < 8; i += 2)
		{
			perpendiculars.add(new Line(fixeds.get(i)).perpendicularAt(quadrilateralTangent.get(i / 2)));
		}

		List<Point> intersectionPoints = new ArrayList<Point>();
		for (int i = 0; i < 4; i++)
		{
			Line l1 = perpendiculars.get(i);
			Line l2 = perpendiculars.get((i + 1) % 4);

			try
			{
				intersectionPoints.add(l1.intersectionWith(l2));
			} catch (Exception e)
			{
				intersectionPoints.add(new SimplePoint(Double.MAX_VALUE, Double.MAX_VALUE));
			}
		}

		double a = BasicPrimitives.distance(quadrilateralTangent.get(0), intersectionPoints.get(0));
		double b = BasicPrimitives.distance(quadrilateralTangent.get(0), intersectionPoints.get(3));

		if (a < b)
		{
			Collections.rotate(fixeds, 2);
			Collections.rotate(quadrilateralTangent, 1);
			Collections.rotate(quadrilateral, 1);
			Collections.rotate(intersectionPoints, 1);
		}

		TangentEdge t0 = new TangentEdge(quadrilateralTangent.get(0), intersectionPoints.get(3));
		TangentEdge t1 = new TangentEdge(quadrilateralTangent.get(1), intersectionPoints.get(1));
		TangentEdge t2 = new TangentEdge(quadrilateralTangent.get(2), intersectionPoints.get(1));
		TangentEdge t3 = new TangentEdge(quadrilateralTangent.get(3), intersectionPoints.get(3));

		t0.setType(MoleculeEdge.EdgeType.VALLEY);
		t1.setType(MoleculeEdge.EdgeType.VALLEY);
		t2.setType(MoleculeEdge.EdgeType.VALLEY);
		t3.setType(MoleculeEdge.EdgeType.VALLEY);

		this.tangents = Arrays.asList(t0, t1, t2, t3);

		for (int i = 0; i < fixeds.size(); i += 2)
		{
			this.tangents.get(i / 2).setExternalFace(fixeds.get(i).getExternal());
			this.tangents.get(i / 2).setInternalFace(face);
		}

		tangentIntersectionPoints = new Pair<Point>(intersectionPoints.get(1), intersectionPoints.get(3));

		Point bissectorIntersectionPoint1;

		{

			Line bissector1 = new Line(tangents.get(0).getEnd(), AnglePrimitives.bissectorDirection(tangents.get(0)
					.getStart(), tangents.get(0).getEnd(), tangents.get(1).getEnd()));

			Line bissector2 = new Line(tangents.get(1).getEnd(), AnglePrimitives.bissectorDirection(tangents.get(1)
					.getStart(), tangents.get(1).getEnd(), tangents.get(0).getEnd()));

			bissectorIntersectionPoint1 = bissector1.intersectionWith(bissector2);

			extras.add(new ExtraEdge(tangentIntersectionPoints.getFirst(), bissectorIntersectionPoint1, EdgeType.VALLEY));
			extras.add(new ExtraEdge(tangentIntersectionPoints.getSecond(), bissectorIntersectionPoint1, EdgeType.VALLEY));

			BissectorEdge bissectorEdge = new BissectorEdge(bissectorIntersectionPoint1, quadrilateral.get(1));
			bissectorEdge.setType(MoleculeEdge.EdgeType.MOUNTAIN);
			bissectors.add(bissectorEdge);
		}

		Point bissectorIntersectionPoint2;

		{

			Line bissector1 = new Line(tangents.get(2).getEnd(), AnglePrimitives.bissectorDirection(tangents.get(2)
					.getStart(), tangents.get(2).getEnd(), tangents.get(3).getEnd()));

			Line bissector2 = new Line(tangents.get(3).getEnd(), AnglePrimitives.bissectorDirection(tangents.get(3)
					.getStart(), tangents.get(3).getEnd(), tangents.get(2).getEnd()));

			bissectorIntersectionPoint2 = bissector1.intersectionWith(bissector2);

			extras.add(new ExtraEdge(tangentIntersectionPoints.getFirst(), bissectorIntersectionPoint2, EdgeType.VALLEY));
			extras.add(new ExtraEdge(tangentIntersectionPoints.getSecond(), bissectorIntersectionPoint2, EdgeType.VALLEY));

			BissectorEdge bissectorEdge = new BissectorEdge(bissectorIntersectionPoint2, quadrilateral.get(3));
			bissectorEdge.setType(MoleculeEdge.EdgeType.MOUNTAIN);
			bissectors.add(bissectorEdge);
		}

		Point middle = new Line(tangentIntersectionPoints.getFirst(), tangentIntersectionPoints.getSecond())
				.intersectionWith(new Line(bissectorIntersectionPoint1, bissectorIntersectionPoint2));

		// IMPORTANT: extras 4 and 5 are the ones that must have its orientation
		// changed
		extras.add(new ExtraEdge(tangentIntersectionPoints.getFirst(), middle, EdgeType.VALLEY));
		extras.add(new ExtraEdge(tangentIntersectionPoints.getSecond(), middle, EdgeType.VALLEY));
		extras.add(new ExtraEdge(bissectorIntersectionPoint1, middle, EdgeType.VALLEY));
		extras.add(new ExtraEdge(bissectorIntersectionPoint2, middle, EdgeType.VALLEY));

		BissectorEdge bissectorEdge = new BissectorEdge(tangentIntersectionPoints.getSecond(), quadrilateral.get(0));
		bissectorEdge.setType(MoleculeEdge.EdgeType.MOUNTAIN);
		bissectors.add(bissectorEdge);

		bissectorEdge = new BissectorEdge(tangentIntersectionPoints.getFirst(), quadrilateral.get(2));
		bissectorEdge.setType(MoleculeEdge.EdgeType.MOUNTAIN);
		bissectors.add(bissectorEdge);
	}

	private void calculatePoints()
	{
		List<WingedEdge> edges = face.inSequence();
		for (int i = 0; i < 8; i++)
		{
			Point common = findCommonPoint(edges.get(i), edges.get((i + 1) % 8));
			if (i % 2 == 0)
			{
				quadrilateralTangent.add(common);
			}
			else
			{
				quadrilateral.add(common);
			}
		}

		Collections.rotate(quadrilateral, 1);
	}

	public static Point findCommonPoint(Edge e1, Edge e2)
	{
		if (e1.getStart().equals(e2.getStart()))
		{
			return e1.getStart();
		}
		if (e1.getStart().equals(e2.getEnd()))
		{
			return e1.getStart();
		}
		if (e1.getEnd().equals(e2.getStart()))
		{
			return e1.getEnd();
		}
		if (e1.getEnd().equals(e2.getEnd()))
		{
			return e1.getEnd();
		}
		return null;
	}

	private void calculateFixedEdges()
	{
		fixeds = new ArrayList<FixedEdge>();
		for (WingedEdge edge : face.inSequence())
		{
			FixedEdge e = new FixedEdge(edge.getStart(), edge.getEnd());

			e.setInternal(face);
			if (edge.isCutEdge())
			{
				e.setType(MoleculeEdge.EdgeType.MOUNTAIN);
			}
			else
			{
				e.setType(MoleculeEdge.EdgeType.VALLEY);
			}

			if (!edge.getCw().equals(face))
			{
				e.setExternal(edge.getCw());
			}
			else
			{
				e.setExternal(edge.getCcw());
			}

			fixeds.add(e);
		}

	}

	public List<TangentEdge> getTangents()
	{
		return tangents;
	}

}
